import { fetchCors, fromHTML, sleep } from '../../common';
import hqIcon from '../assets/icons/hq.svg?raw';
import { offAlbumRoute, onAlbumRoute } from '../glue/routing';
import { createButtonElement } from '../glue/ui/sidebar';
import { getAlbum } from '../services';

interface Quality {
	'FIRST-SEGMENT-URI': string;
	'AUDIO-FORMAT-ID': string;
	'CHANNEL-USAGE'?: string;
	'CHANNEL-COUNT': string;
	'BIT-RATE'?: number;
	'SAMPLE-RATE': number;
	'BIT-DEPTH': number;
	'IS-ATMOS'?: boolean;
	'__ACTUAL__'?: true;
}

const FORMAT_ORDER = [ 'ec+3', 'alac', 'aac ', 'aach' ];
const CHANNEL_USAGE_ORDER = [ 'BINAURAL', 'DOWNMIX' ];

let globalJob: AbortController | null = null;

export const checkQualitiesButtonEl = createButtonElement('Check Qualities', hqIcon);

checkQualitiesButtonEl.addEventListener('click', async () => {
	if (globalJob) return;
	const job = new AbortController();
	globalJob = job;

	const country = location.pathname.split('/')[1];
	const albumId = location.pathname.split('/')[4];

	const album = await getAlbum(albumId, country);
	if (!album) return;

	const trackEls = Array.from(document.querySelectorAll<HTMLElement>('.songs-list-row__song-wrapper'));

	for (const track of album.relationships.tracks.data) {
		if (job.signal.aborted) break;
		if (track.type !== 'songs') continue;

		const trackEl = trackEls.shift();
		if (!trackEl) continue;

		trackEl.querySelector('.ame-track-quality')?.remove();

		if (!track.attributes.extendedAssetUrls) {
			trackEl.append(fromHTML(`<span class="ame-track-quality ame-color-warning">[unavailable]</span>`));
			continue;
		}

		const manifest = await (await fetchCors(track.attributes.extendedAssetUrls.enhancedHls)).text();
		await sleep(150);

		let data: Record<string, Quality> | null = null;
		for (const line of manifest.split('\n')) {
			if (!line.startsWith('#EXT-X-SESSION-DATA:DATA-ID="com.apple.hls.audioAssetMetadata"')) continue;
			data = JSON.parse(atob(line.split('VALUE=')[1].slice(1, -1)));
			break;
		}

		if (!data) throw new Error('Could not find data from track manifest.');

		const qualities = Object.values(data);
		qualities.sort(sortQuality);

		const realInfo = await fetchRealAlacQuality(track.attributes.extendedAssetUrls.enhancedHls, qualities);
		if (realInfo) qualities.push(realInfo);

		qualities.sort(sortQuality);
		const displayQuality = qualities.find(quality => parseInt(quality['CHANNEL-COUNT']) <= 2)!;

		trackEl.append(fromHTML(`<span class="ame-track-quality ame-color-tertiary" title="${qualities.map(formatQuality).join('\n')}">${formatQuality(displayQuality)}</span>`));
	}
});

onAlbumRoute(() => {
	globalJob?.abort();
	globalJob = null;
});

offAlbumRoute(() => {
	globalJob?.abort();
	globalJob = null;
});

function sortQuality(a: Quality, b: Quality): number {
	return FORMAT_ORDER.indexOf(a['AUDIO-FORMAT-ID']) - FORMAT_ORDER.indexOf(b['AUDIO-FORMAT-ID']) ||
		b['BIT-DEPTH'] - a['BIT-DEPTH'] ||
		b['SAMPLE-RATE'] - a['SAMPLE-RATE'] ||
		(b['BIT-RATE'] ?? NaN) - (a['BIT-RATE'] ?? NaN) ||
		CHANNEL_USAGE_ORDER.indexOf(a['CHANNEL-USAGE'] ?? '') - CHANNEL_USAGE_ORDER.indexOf(b['CHANNEL-USAGE'] ?? '') ||
		-Number(a['__ACTUAL__']);
}

function formatQuality(quality: Quality): string {
	const parts = [];

	parts.push(quality['AUDIO-FORMAT-ID']);
	if (quality['CHANNEL-COUNT']) parts.push(`${quality['CHANNEL-COUNT']}ch`);
	if (quality['BIT-RATE']) parts.push(`${Math.floor(Number(quality['BIT-RATE']) / 1000)}kbps`);
	if (quality['BIT-DEPTH']) parts.push(`${quality['BIT-DEPTH']}bit`);
	if (quality['SAMPLE-RATE']) parts.push(`${Math.floor(Number(quality['SAMPLE-RATE']) / 1000)}kHz`);
	if (quality['CHANNEL-USAGE']) parts.push(quality['CHANNEL-USAGE'].toLowerCase());
	if (quality['IS-ATMOS']) parts.push('atmos');
	if (quality['__ACTUAL__']) parts.push('[ACTUAL]');

	return parts.join('  ');
}

async function fetchRealAlacQuality(manifestUrl: string, qualities: Quality[]): Promise<Quality | null> {
	const baseUrl = manifestUrl.split('/').slice(0, -1).join('/');
	const firstSegmentUrl = qualities.filter(quality => quality['AUDIO-FORMAT-ID'] === 'alac' && parseInt(quality['CHANNEL-COUNT']) <= 2).at(-1)?.['FIRST-SEGMENT-URI'];
	if (!firstSegmentUrl) return null;

	const res = await (await fetchCors(`${baseUrl}/${firstSegmentUrl}`, {
		headers: {
			'Range': 'bytes=0-16384'
		}
	}));

	const view = new DataView(await res.arrayBuffer());

	if (view.getInt32(4) !== 0x66_74_79_70 || // ftyp
		view.getInt32(8) !== 0x69_73_6F_35) { // iso5
		return null;
	}

	let pos = 0;
	let loops = 0;
	while (pos < view.byteLength) {
		const atomSize = view.getInt32(pos);
		const atomType = view.getInt32(pos + 4);

		switch (atomType) {
			case 0x6D_6F_6F_76: // moov
			case 0x74_72_61_6B: // trak
			case 0x6D_64_69_61: // mdia
			case 0x6D_69_6E_66: // minf
			case 0x73_74_62_6C: // stbl
				pos += 8; // Move inside atom.
				break;
			case 0x73_74_73_64: // stsd
				pos += 8 + 8; // Move inside atom.
				break;
			case 0x65_6E_63_61: // enca
				pos += 8 + 28; // Move inside atom.
			case 0x61_6C_61_63: // alac
				return {
					'FIRST-SEGMENT-URI': firstSegmentUrl,
					'AUDIO-FORMAT-ID': 'alac',
					'CHANNEL-COUNT': view.getUint8(pos + 8 + 13).toString(),
					'BIT-DEPTH': view.getUint8(pos + 8 + 9),
					'SAMPLE-RATE': view.getInt32(pos + 8 + 24),
					'__ACTUAL__': true
				};
			default:
				pos += atomSize;
				break;
		}

		if (loops++ > 100) break;
	}

	return null;
}
